/* *
 *
 *  (c) 2010-2019 Torstein Honsi
 *
 *  License: www.highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
"use strict";
import H from "./Globals.js";
import U from "./Utilities.js";
var extend = U.extend,
  pick = U.pick;
var charts = H.charts,
  noop = H.noop,
  Pointer = H.Pointer;
/* eslint-disable no-invalid-this, valid-jsdoc */
// Support for touch devices
extend(
  Pointer.prototype,
  /** @lends Pointer.prototype */ {
    /**
     * Run translation operations
     *
     * @private
     * @function Highcharts.Pointer#pinchTranslate
     *
     * @param {Array<*>} pinchDown
     *
     * @param {Array<*>} touches
     *
     * @param {*} transform
     *
     * @param {*} selectionMarker
     *
     * @param {*} clip
     *
     * @param {*} lastValidTouch
     *
     * @return {void}
     */
    pinchTranslate: function (
      pinchDown,
      touches,
      transform,
      selectionMarker,
      clip,
      lastValidTouch
    ) {
      if (this.zoomHor) {
        this.pinchTranslateDirection(
          true,
          pinchDown,
          touches,
          transform,
          selectionMarker,
          clip,
          lastValidTouch
        );
      }
      if (this.zoomVert) {
        this.pinchTranslateDirection(
          false,
          pinchDown,
          touches,
          transform,
          selectionMarker,
          clip,
          lastValidTouch
        );
      }
    },
    /**
     * Run translation operations for each direction (horizontal and vertical)
     * independently.
     *
     * @private
     * @function Highcharts.Pointer#pinchTranslateDirection
     *
     * @param {boolean} horiz
     *
     * @param {Array<*>} pinchDown
     *
     * @param {Array<*>} touches
     *
     * @param {*} transform
     *
     * @param {*} selectionMarker
     *
     * @param {*} clip
     *
     * @param {*} lastValidTouch
     *
     * @param {number|undefined} [forcedScale=1]
     *
     * @return {void}
     */
    pinchTranslateDirection: function (
      horiz,
      pinchDown,
      touches,
      transform,
      selectionMarker,
      clip,
      lastValidTouch,
      forcedScale
    ) {
      var chart = this.chart,
        xy = horiz ? "x" : "y",
        XY = horiz ? "X" : "Y",
        sChartXY = "chart" + XY,
        wh = horiz ? "width" : "height",
        plotLeftTop = chart["plot" + (horiz ? "Left" : "Top")],
        selectionWH,
        selectionXY,
        clipXY,
        scale = forcedScale || 1,
        inverted = chart.inverted,
        bounds = chart.bounds[horiz ? "h" : "v"],
        singleTouch = pinchDown.length === 1,
        touch0Start = pinchDown[0][sChartXY],
        touch0Now = touches[0][sChartXY],
        touch1Start = !singleTouch && pinchDown[1][sChartXY],
        touch1Now = !singleTouch && touches[1][sChartXY],
        outOfBounds,
        transformScale,
        scaleKey,
        setScale = function () {
          // Don't zoom if fingers are too close on this axis
          if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
            scale =
              forcedScale ||
              Math.abs(touch0Now - touch1Now) /
                Math.abs(touch0Start - touch1Start);
          }
          clipXY = (plotLeftTop - touch0Now) / scale + touch0Start;
          selectionWH = chart["plot" + (horiz ? "Width" : "Height")] / scale;
        };
      // Set the scale, first pass
      setScale();
      // The clip position (x or y) is altered if out of bounds, the selection
      // position is not
      selectionXY = clipXY;
      // Out of bounds
      if (selectionXY < bounds.min) {
        selectionXY = bounds.min;
        outOfBounds = true;
      } else if (selectionXY + selectionWH > bounds.max) {
        selectionXY = bounds.max - selectionWH;
        outOfBounds = true;
      }
      // Is the chart dragged off its bounds, determined by dataMin and
      // dataMax?
      if (outOfBounds) {
        // Modify the touchNow position in order to create an elastic drag
        // movement. This indicates to the user that the chart is responsive
        // but can't be dragged further.
        touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
        if (!singleTouch) {
          touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
        }
        // Set the scale, second pass to adapt to the modified touchNow
        // positions
        setScale();
      } else {
        lastValidTouch[xy] = [touch0Now, touch1Now];
      }
      // Set geometry for clipping, selection and transformation
      if (!inverted) {
        clip[xy] = clipXY - plotLeftTop;
        clip[wh] = selectionWH;
      }
      scaleKey = inverted ? (horiz ? "scaleY" : "scaleX") : "scale" + XY;
      transformScale = inverted ? 1 / scale : scale;
      selectionMarker[wh] = selectionWH;
      selectionMarker[xy] = selectionXY;
      transform[scaleKey] = scale;
      transform["translate" + XY] =
        transformScale * plotLeftTop +
        (touch0Now - transformScale * touch0Start);
    },
    /**
     * Handle touch events with two touches
     *
     * @private
     * @function Highcharts.Pointer#pinch
     *
     * @param {Highcharts.PointerEventObject} e
     *
     * @return {void}
     */
    pinch: function (e) {
      var self = this,
        chart = self.chart,
        pinchDown = self.pinchDown,
        touches = e.touches,
        touchesLength = touches.length,
        lastValidTouch = self.lastValidTouch,
        hasZoom = self.hasZoom,
        selectionMarker = self.selectionMarker,
        transform = {},
        fireClickEvent =
          touchesLength === 1 &&
          ((self.inClass(e.target, "highcharts-tracker") &&
            chart.runTrackerClick) ||
            self.runChartClick),
        clip = {};
      // Don't initiate panning until the user has pinched. This prevents us
      // from blocking page scrolling as users scroll down a long page
      // (#4210).
      if (touchesLength > 1) {
        self.initiated = true;
      }
      // On touch devices, only proceed to trigger click if a handler is
      // defined
      if (hasZoom && self.initiated && !fireClickEvent) {
        e.preventDefault();
      }
      // Normalize each touch
      [].map.call(touches, function (e) {
        return self.normalize(e);
      });
      // Register the touch start position
      if (e.type === "touchstart") {
        [].forEach.call(touches, function (e, i) {
          pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
        });
        lastValidTouch.x = [
          pinchDown[0].chartX,
          pinchDown[1] && pinchDown[1].chartX,
        ];
        lastValidTouch.y = [
          pinchDown[0].chartY,
          pinchDown[1] && pinchDown[1].chartY,
        ];
        // Identify the data bounds in pixels
        chart.axes.forEach(function (axis) {
          if (axis.zoomEnabled) {
            var bounds = chart.bounds[axis.horiz ? "h" : "v"],
              minPixelPadding = axis.minPixelPadding,
              min = axis.toPixels(
                Math.min(pick(axis.options.min, axis.dataMin), axis.dataMin)
              ),
              max = axis.toPixels(
                Math.max(pick(axis.options.max, axis.dataMax), axis.dataMax)
              ),
              absMin = Math.min(min, max),
              absMax = Math.max(min, max);
            // Store the bounds for use in the touchmove handler
            bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
            bounds.max = Math.max(
              axis.pos + axis.len,
              absMax + minPixelPadding
            );
          }
        });
        self.res = true; // reset on next move
        // Optionally move the tooltip on touchmove
      } else if (self.followTouchMove && touchesLength === 1) {
        this.runPointActions(self.normalize(e));
        // Event type is touchmove, handle panning and pinching
      } else if (pinchDown.length) {
        // can be 0 when releasing, if touchend
        // fires first
        // Set the marker
        if (!selectionMarker) {
          self.selectionMarker = selectionMarker = extend(
            {
              destroy: noop,
              touch: true,
            },
            chart.plotBox
          );
        }
        self.pinchTranslate(
          pinchDown,
          touches,
          transform,
          selectionMarker,
          clip,
          lastValidTouch
        );
        self.hasPinched = hasZoom;
        // Scale and translate the groups to provide visual feedback during
        // pinching
        self.scaleGroups(transform, clip);
        if (self.res) {
          self.res = false;
          this.reset(false, 0);
        }
      }
    },
    /**
     * General touch handler shared by touchstart and touchmove.
     *
     * @private
     * @function Highcharts.Pointer#touch
     *
     * @param {Highcharts.PointerEventObject} e
     *
     * @param {boolean} [start]
     *
     * @return {void}
     */
    touch: function (e, start) {
      var chart = this.chart,
        hasMoved,
        pinchDown,
        isInside;
      if (chart.index !== H.hoverChartIndex) {
        this.onContainerMouseLeave({ relatedTarget: true });
      }
      H.hoverChartIndex = chart.index;
      if (e.touches.length === 1) {
        e = this.normalize(e);
        isInside = chart.isInsidePlot(
          e.chartX - chart.plotLeft,
          e.chartY - chart.plotTop
        );
        if (isInside && !chart.openMenu) {
          // Run mouse events and display tooltip etc
          if (start) {
            this.runPointActions(e);
          }
          // Android fires touchmove events after the touchstart even if
          // the finger hasn't moved, or moved only a pixel or two. In iOS
          // however, the touchmove doesn't fire unless the finger moves
          // more than ~4px. So we emulate this behaviour in Android by
          // checking how much it moved, and cancelling on small
          // distances. #3450.
          if (e.type === "touchmove") {
            pinchDown = this.pinchDown;
            hasMoved = pinchDown[0]
              ? Math.sqrt(
                  // #5266
                  Math.pow(pinchDown[0].chartX - e.chartX, 2) +
                    Math.pow(pinchDown[0].chartY - e.chartY, 2)
                ) >= 4
              : false;
          }
          if (pick(hasMoved, true)) {
            this.pinch(e);
          }
        } else if (start) {
          // Hide the tooltip on touching outside the plot area (#1203)
          this.reset();
        }
      } else if (e.touches.length === 2) {
        this.pinch(e);
      }
    },
    /**
     * @private
     * @function Highcharts.Pointer#onContainerTouchStart
     *
     * @param {Highcharts.PointerEventObject} e
     *
     * @return {void}
     */
    onContainerTouchStart: function (e) {
      this.zoomOption(e);
      this.touch(e, true);
    },
    /**
     * @private
     * @function Highcharts.Pointer#onContainerTouchMove
     *
     * @param {Highcharts.PointerEventObject} e
     *
     * @return {void}
     */
    onContainerTouchMove: function (e) {
      this.touch(e);
    },
    /**
     * @private
     * @function Highcharts.Pointer#onDocumentTouchEnd
     *
     * @param {Highcharts.PointerEventObject} e
     *
     * @return {void}
     */
    onDocumentTouchEnd: function (e) {
      if (charts[H.hoverChartIndex]) {
        charts[H.hoverChartIndex].pointer.drop(e);
      }
    },
  }
);
